/*
 * Created by: 
 * Created: Thursday, April 19, 2007
 */

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Security.Policy;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.UI;
using Edge.Common.ControlFlow;
using Edge.Common.TextTemplates.Exceptions;
using Edge.Common.ControlFlow;
using Edge.Common.TextTemplates.Exceptions;
using log4net;

namespace Edge.Common.TextTemplates.Impl.ASPNET
{


    public class ControlsFactory
    {
        private static readonly ILog log = LogManager.GetLogger(MethodInfo.GetCurrentMethod().DeclaringType);
        private const string FILE_PREFIX = "file:///";
        private readonly string templatesDir = Directory.GetCurrentDirectory();
        private IAppHost appHost = null;
        private readonly Dictionary<string, TypeLocationInfo> infosCache = new Dictionary<string, TypeLocationInfo>();

        private bool needRealoding = false;
        private Assembly[] references;

        private FileSystemWatcher watcher;


        public ControlsFactory()
        {
        }


        public ControlsFactory(bool needRealoding)
        {
            this.needRealoding = needRealoding;
        }

        public ControlsFactory(string templatesDir)
        {
            this.templatesDir = StreepDirPath(templatesDir);
        }


        public ControlsFactory(string templatesDir, bool needRealoding, Assembly[] references)
        {
            this.templatesDir = StreepDirPath(templatesDir);
            this.needRealoding = needRealoding;
            this.references = references;
        }

        protected string StreepDirPath(string path)
        {
            if (path == null)
                throw new ArgumentException("path");
            if (path.EndsWith(Path.DirectorySeparatorChar.ToString()))
                return path.Substring(0, path.Length - 1);
            return path;
        }

        public Control Create(string controlName)
        {
            Type controlType = GetControlTypeInternal(controlName);

            try
            {
                return (Control)Activator.CreateInstance(controlType);
            }
            catch (Exception ex)
            {
                throw new PrepareTemplateException("Error at preparing template " + controlName, ex);
            }
        }

        public Type GetControlType(string controlName)
        {

            return GetControlTypeInternal(controlName);

        }

        private IAppHost GetAppHost()
        {
            if (appHost != null)
                return appHost;

            lock (this)
            {
                if (appHost == null)
                {
                    string templatedCodeBase = templatesDir + Path.DirectorySeparatorChar + "bin";

                    if (!Directory.Exists(templatedCodeBase))
                        Directory.CreateDirectory(templatedCodeBase);

//                    #region Logging
//
//                    if (log.IsDebugEnabled)
//                    {
//                        log.Debug("Temapltes CodeBase is " + templatedCodeBase);
//                    }
//
//                    #endregion


                    CopyAssembly(typeof(ControlsFactory).Assembly, templatedCodeBase);

                    if (references != null)
                    {
                        foreach (Assembly assembly in references)
                        {
                            CopyAssembly(assembly, templatedCodeBase);
                        }
                    }

                    object hostObject = null;

                    if (HttpContext.Current != null)
                    {
                        AppDomainSetup setup = new AppDomainSetup();
                        //                        setup.ApplicationBase = templatesDir;
                        setup.ApplicationBase = templatedCodeBase;

                        setup.PrivateBinPath = "";
                        setup.PrivateBinPathProbe = "*";
                        setup.ShadowCopyFiles = "true";
                        setup.ConfigurationFile = "Web.config";
                        //                        setup.ApplicationBase = (new Uri(appPath, true)).ToString();
                        //                        setup.ApplicationName = APP_NAME;
                        Assembly entryAssembly = Assembly.GetEntryAssembly();
                        setup.DisallowCodeDownload = true;

                        AppDomain domain = AppDomain.CreateDomain("TextTemplates " + templatesDir, /*new Evidence()*/ null, setup);
                        //                        domain.DomainUnload += delegate(object sender, EventArgs e)
                        //                                                   {
                        //                                                       if(log.IsInfoEnabled)
                        //                                                       {
                        //                                                        log.Info("unloaded domain created for AppHost");
                        //                                                       }
                        //                                                   };
                        HostCreator hostCreator = (HostCreator)domain.CreateInstanceAndUnwrap(typeof(HostCreator).Assembly.FullName, typeof(HostCreator).FullName);
                        hostObject = hostCreator.Create(templatesDir);
                    }
                    else

                        hostObject = ApplicationHost.CreateApplicationHost(typeof(AppHost), "/", templatesDir);

                    //                    TODO debug assembly loading

                    AppHost appHostCasted = hostObject as AppHost;
                    if (appHostCasted != null)
                        appHost = appHostCasted;
                    else
                    {
                        throw new InvalidOperationException("Error at creating AppHost");
                        //appHost = new AppHostProxyAdapter(hostObject);
                    }


                    if (needRealoding)
                    {
                        // TODO use AppDomain.OnUload event

                        watcher = new FileSystemWatcher();
                        watcher.Path = templatesDir;

                        watcher.Filter = "*.*";
                        watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName;

                        watcher.Changed += watcher_Changed;
                        watcher.Created += watcher_Changed;
                        watcher.Deleted += watcher_Changed;
                        watcher.Renamed += watcher_Renamed;

                        watcher.EnableRaisingEvents = true;
                    }

                }
            }

            return appHost;

        }

        protected void CopyAssembly(Assembly assembly, string targetDir)
        {
            if (targetDir == null)
                throw new ArgumentException("targetDir");

            Assert.Check(!targetDir.EndsWith(Path.DirectorySeparatorChar.ToString()));

            string assemblyPath = assembly.CodeBase;
            if (assemblyPath.StartsWith(FILE_PREFIX))
                assemblyPath = assemblyPath.Substring(FILE_PREFIX.Length);

            string targetPath = targetDir + Path.DirectorySeparatorChar + assembly.GetName().Name + ".dll";
            File.Copy(assemblyPath, targetPath, true);

//            #region Logging
//
//            if (log.IsDebugEnabled)
//            {
//                log.Debug("Copied  " + assemblyPath + " to " + targetPath);
//            }
//
//            #endregion

        }

        private void watcher_Renamed(object sender, RenamedEventArgs e)
        {
            RaiseReload();
        }

        private void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            RaiseReload();
        }

        protected void RaiseReload()
        {
            lock (this)
            {
                appHost = null;
                infosCache.Clear();
            }
        }

        private Assembly CurrentDomain_OnTypeResolve(object sender, ResolveEventArgs args)
        {
            return null;
        }

        //        private static Assembly CurrentDomain_OnAssemblyResolve(object sender, ResolveEventArgs args)
        //        {
        //            return Assembly.GetExecutingAssembly().FullName == args.Name ? Assembly.GetExecutingAssembly() : null;
        //        }

        private Type GetControlTypeInternal(string controlName)
        {
            try
            {
                IAppHost host = GetAppHost();

                TypeLocationInfo info = null;

                if (!infosCache.ContainsKey(controlName))
                {
                    lock (this)
                    {
                        if (!infosCache.ContainsKey(controlName))
                        {
                            info = host.GetLocationInfo(controlName);
                            infosCache[controlName] = info;
                        }
                    }
                }

                info = infosCache[controlName];

                Assembly assembly = Assembly.LoadFrom(info.AssemblyPath);

                return assembly.GetType(info.TypeName);
            }
            catch (HttpCompileException ex)
            {
                StringBuilder compilationErrorMessage = new StringBuilder();
                compilationErrorMessage.Append("Compilation errors:");
                compilationErrorMessage.Append(Environment.NewLine);
                foreach (CompilerError compilerError in ex.Results.Errors)
                {
                    // TODO replace file name to name in termplates directory
                    compilationErrorMessage.Append(compilerError.ToString());
                    compilationErrorMessage.Append(Environment.NewLine);
                }



                throw new PrepareTemplateException("Error at preparing template " + controlName + Environment.NewLine + compilationErrorMessage, ex);
            }
            catch (Exception ex)
            {
                throw new PrepareTemplateException("Error at prepering template " + controlName, ex);
            }
        }
    }

    public class HostCreator : MarshalByRefObject
    {
        public AppHost Create(string templatesDir)
        {
            return (AppHost)ApplicationHost.CreateApplicationHost(typeof(AppHost), "/", templatesDir);
        }
    }
}
